/*
   Copyright (C) 2006 Kai Sterker <kaisterker@linuxgames.com>
   Adapted as C library 2009 Klaus Blindert <klaus.blindert@web.de>

   Originally part of the Adonthell Project http://adonthell.linuxgames.com.
   Part of the libABF project.

   libABF is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the
   Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/


/**
 * @file abf_format_xml.c
 * @author Kai Sterker <kaisterker@linuxgames.com>
 * @author Klaus Blindert <klaus.blindert@web.de>
 *
 * @brief Read/write xml-formatted data files.
 */

#include "abf_format_xml.h"

#include <assert.h>
#include <libxml/parser.h>
#include <memory.h>
#include <strings.h>

/* for converting binary to ascii */
const char *Bin2Hex = "0123456789ABCDEF";

/* Name of xml data file root node */
#define XML_ROOT_NODE "Data"

typedef enum { UNDEF, DATA, LIST, PARAM } context_state;

/**
 * Context for the sax parser
 */
typedef struct 
{
    /** Storage for parameters */
    abf_flat_t *Record;
    /** id attribute of current element */
    char* Id;
    /** value of primitive element */
    char* Value;
    /** type of current element */
    abf_data_type Type;
    /** current state of sax parser */
    u_int8 State;
} data_sax_context;


typedef struct
{
    data_sax_context **stack;
    u_int32 stack_size;
    u_int32 stack_capacity;
    int error;
} sax_context_t;


void sax_context_init(sax_context_t* sax_context);
void sax_context_deinit(sax_context_t* sax_context);
void sax_context_grow(sax_context_t* sax_context);
void sax_context_pop(sax_context_t* sax_context);
data_sax_context* sax_context_push (sax_context_t* sax_context, abf_flat_t *record);

data_sax_context* sax_context_push (sax_context_t* sax_context, abf_flat_t *record)
{
    data_sax_context* context = (data_sax_context*) malloc (sizeof (data_sax_context));
    context->Id = 0;
    context->Record = record;
    context->State = UNDEF;

    if (sax_context->stack_size == sax_context->stack_capacity)
        sax_context_grow (sax_context);

    sax_context->stack[sax_context->stack_size] = context;
    ++sax_context->stack_size;

    return context;
}

void sax_context_pop(sax_context_t* sax_context)
{
    data_sax_context* context = sax_context->stack[sax_context->stack_size-1];
    if (context->Id)
        free(context->Id);
    free(context);
    sax_context->stack_size -= 1;
}

void sax_context_init(sax_context_t* sax_context)
{
    sax_context->error = ABF_OK;
    sax_context->stack = 0;
    sax_context->stack_size = 0;
    sax_context->stack_capacity = 0;
}

void sax_context_deinit(sax_context_t* sax_context)
{
    while (sax_context->stack_size)
        sax_context_pop (sax_context);
    free(sax_context->stack);
    sax_context->stack = 0;
    sax_context->stack_capacity = 0;
}


void sax_context_grow(sax_context_t* sax_context)
{
    data_sax_context** _context_stack = sax_context->stack;
    sax_context->stack_capacity = sax_context->stack_capacity ? (sax_context->stack_capacity*2) : 5;
    sax_context->stack = (data_sax_context**) malloc(sax_context->stack_capacity * sizeof (data_sax_context*));

    bzero(sax_context->stack, sax_context->stack_capacity);
    if( sax_context->stack_size )
    {
        memcpy(sax_context->stack, _context_stack, sax_context->stack_size*sizeof(data_sax_context*));
        free(_context_stack);
    }
}


// safely convert string to unsigned integer
static u_int32 string_to_uint (const char* value, u_int32 max)
{
    char *end = NULL;
    u_int32 intval = (u_int32) strtoul (value, &end, 10);

    // parsing okay?
    if (*end == '\0')
    {
        // in range?
        if (intval > max)
        {
            fprintf(stderr, "*** string_to_uint: integer overflow: value '%i' > max '%i'!\n", intval, max);
            return max;
        }
    }
    else
    {
        // value is not an integer
        fprintf (stderr, "*** string_to_uint: Can't convert '%s' to unsigned integer!\n", value);
        return 0;
    }

    return intval;
}

// safely convert string to signed integer
static s_int32 string_to_sint (const char* value, s_int32 min, s_int32 max)
{
    char *end = NULL;
    s_int32 intval = (s_int32) strtol (value, &end, 10);

    // parsing okay?
    if (*end == '\0')
    {
        // in range?
        if (intval < min)
        {
            fprintf(stderr, "*** string_to_sint: integer underflow: value '%i' < min '%i'!\n", intval, min);
            return min;
        }

        if (intval > max)
        {
            fprintf(stderr, "*** string_to_sint: integer overflow: value '%i' > max '%i'!\n", intval, max);
            return max;
        }
    }
    else
    {
        // value is not an integer
        fprintf (stderr, "*** string_to_sint: Can't convert '%s' to signed integer!\n", value);
        return -1;
    }

    return intval;
}

// convert primitive params
static void param_to_value (const data_sax_context *context)
{
    const char *value = context->Value;
    switch (context->Type)
    {
        case ABF_T_BOOL:
        {
            abf_put_bool (context->Record, context->Id, string_to_sint (value, 0, 1) == 1);
            break;
        }
        case ABF_T_DOUBLE:
        {
            abf_put_double (context->Record, context->Id, strtod (value, NULL));
            break;
        }
        case ABF_T_FLOAT:
        {
            abf_put_float (context->Record, context->Id, strtod (value, NULL));
            break;
        }
        case ABF_T_CHAR:
        case ABF_T_SINT8:
        {
            abf_put_sint8 (context->Record, context->Id, (s_int8) string_to_sint (value, INT8_MIN, INT8_MAX));
            break;
        }
        case ABF_T_SINT16:
        {
            abf_put_sint16 (context->Record, context->Id, (s_int16) string_to_sint (value, INT16_MIN, INT16_MAX));
            break;
        }
        case ABF_T_SINT32:
        {
            abf_put_sint32 (context->Record, context->Id, (s_int32) string_to_sint (value, INT32_MIN, INT32_MAX));
            break;
        }
        case ABF_T_STRING:
        {
            abf_put_string (context->Record, context->Id, value);
            break;
        }
        case ABF_T_UINT8:
        {
            abf_put_uint8 (context->Record, context->Id, (u_int8) string_to_uint (value, UINT8_MAX));
            break;
        }
        case ABF_T_UINT16:
        {
            abf_put_uint16 (context->Record, context->Id, (u_int16) string_to_uint (value, UINT16_MAX));
            break;
        }
        case ABF_T_UINT32:
        {
            abf_put_uint32 (context->Record, context->Id, (u_int32) string_to_uint (value, UINT32_MAX));
            break;
        }
        default:
        {
            fprintf (stderr, "*** param_to_value: invalid type for value '%s'!\n", value);
            break;
        }
    }
}

static xmlEntityPtr data_get_entity(void *user_data, const xmlChar *name)
{
    return xmlGetPredefinedEntity(name);
}

/**
 * Called when an opening tag has been processed.
 * @param ctx the parser context
 * @param name The element name
 * @param atts Element attributes
 */
static void data_start_element (void *ctx, const xmlChar *name, const xmlChar **atts)
{
    u_int32 i;
    abf_flat_t *record = 0;
    sax_context_t* sax_context = (sax_context_t*) ctx;
    data_sax_context *context = sax_context->stack[sax_context->stack_size-1];
    if (sax_context->error!=ABF_OK)
        return;

    switch (context->State)
    {
        // root node
        case UNDEF:
        {
            // check for root node
            if (strcmp ((char*) name, XML_ROOT_NODE) == 0)
            {
                context->State = DATA;
                // get attribute "cs", if present
                if (atts != NULL)
                {
/*                    for (u_int32 i = 0; atts[i] != NULL; i += 2)
                    {
                        if (atts[i][0] == 'c' && atts[i][1] == 's')
                        {
                          context->Checksum = (char*) atts[i+1];
                          break;
                        }
                    }
*/
                }
            }
            else
            {
                fprintf (stderr, "*** data_start_element: expected <" XML_ROOT_NODE ">, but got <%s>!\n", (char*) name);
            }
            break;
        }
        // start reading list or parameter
        case DATA: // fall through
        case LIST:
        {
            // get type of element
            abf_data_type type = abf_flat_gettypeforname ((char*) name);

            // reset id
            if (context->Id)
                free(context->Id);
            context->Id = 0;

            // get attribute "id", if present
            if (atts != NULL)
            {
                for (i = 0; atts[i] != NULL; i += 2)
                {
                    if (atts[i][0] == 'i' && atts[i][1] == 'd')
                    {
                        // store checksum in the Id field
                        context->Id = strdup((char*) atts[i+1]);
                        break;
                    }
                }
            }
            if (context->Id==NULL)
            {
                fprintf(stderr,"*** data_start_element: expected 'id' attribute, but got nothing!\n");
                sax_context->error = ABF_E_ERROR;
                break;
            }
            // set type of parameter
            context->Type = type;

            // check whether we got list
            switch (type)
            {
              // error
              case ABF_T_UNKNOWN:
              {
                return;
              }
              // list type
              case ABF_T_FLAT:
              {
                record = abf_create();

                /* create child context for sublist */
                /* printf("push:%d\n",context_stack_size); */
                context = sax_context_push (sax_context, record);
                context->State = LIST;
                break;
              }
              // primitive type
              default:
              {
                  context->Value = 0;
                  context->State = PARAM;
                  break;
              }
            }
            break;
        }
        // error
        default:
        {
            fprintf (stderr, "*** data_start_element: entering <%s> with invalid state!\n", (char*) name);
            break;
        }
    }
}

/**
 * Called when the end of an element has been detected.
 * @param ctx the parser context
 * @param name the element name
 */
static void data_end_element (void *ctx, const xmlChar *name)
{
    data_sax_context *parent = 0;
    sax_context_t* sax_context = (sax_context_t*) ctx;
    data_sax_context *context = sax_context->stack[sax_context->stack_size-1];
    if (sax_context->error!=ABF_OK)
        return;

    switch (context->State)
    {
        // finished reading parameter
        case PARAM:
        {
          // convert and add value of param just read
            param_to_value (context);
            free(context->Value);
            context->Value = 0;
            context->State = LIST;
            break;
        }
        // finished reading list
        case LIST:
        {
            // this could also be </Data>, in which case we do nothing
            if (strcmp ((char*) name, XML_ROOT_NODE) != 0)
            {
                // get parent context
                parent = sax_context->stack[sax_context->stack_size-2];
                assert (parent);

                // add completed list to parent list
                abf_put_flat (parent->Record, parent->Id, context->Record);

                // cleanup
                abf_destroy(context->Record);
                sax_context_pop(sax_context);
//                 printf("pop:%d\n",context_stack_size);
            }
            break;
        }
        // finished reading configuration
        case DATA:
        {
            context->State = UNDEF;
            break;
        }
        // error
        default:
        {
            fprintf (stderr, "*** data_end_element: leaving </%s> with invalid state!\n", (char*) name);
            break;
        }
    }
}

/**
 * Receiving some chars from the parser.
 * @param ctx the parser context
 * @param content content of xml tag
 * @param len length of content
 */
static void data_read_characters (void *ctx, const xmlChar *content, int len)
{
    char* buffer;
    int value_len = 0;
    sax_context_t* sax_context = (sax_context_t*) ctx;
    if (sax_context->error!=ABF_OK)
        return;

    data_sax_context *context = sax_context->stack[sax_context->stack_size-1];

    // only read characters if we're inside a primitive type
    if (context->State == PARAM)
    {
        // store value first and assign when closing element, as
        // 'data_read_characters' is not called for empty elements.
        if( context->Value )
            value_len = strlen(context->Value);
        buffer = (char*) malloc(value_len + len + 1);
        if( context->Value )
            memcpy(buffer,context->Value,value_len);
        memcpy(buffer + value_len,content,len);
        buffer[value_len + len]='\0';
        if (context->Value)
            free(context->Value);
        context->Value = buffer;
    }
}

/**
 * Display and format error messages, gives file, line, position and
 * extra parameters.
 * @param ctx the parser context
 * @param msg error message to display
 * @param ... extra parameters for the message display
 */
static void data_parse_error (void *ctx, const char *msg, ...)
{
    va_list args;
    fprintf (stderr, "*** data_reader: ");
    va_start (args, msg);
    vfprintf (stderr, msg, args);
    va_end (args);
}

/**
 * Callbacks for the sax parser
 */
xmlSAXHandler data_sax_handler = {
    NULL, /* internalSubset */
    NULL, /* isStandalone */
    NULL, /* hasInternalSubset */
    NULL, /* hasExternalSubset */
    NULL, /* resolveEntity */
    data_get_entity,
    NULL, /* entityDecl */
    NULL, /* notationDecl */
    NULL, /* attributeDecl */
    NULL, /* elementDecl */
    NULL, /* unparsedEntityDecl */
    NULL, /* setDocumentLocator */
    NULL, /* startDocument */
    NULL, /* endDocument */
    data_start_element,
    data_end_element,
    NULL, /* reference */
    data_read_characters,
    NULL, /* ignorableWhitespace */
    NULL, /* processingInstruction */
    NULL, /* parseComment */
    NULL, /* xmlParserWarning */
    data_parse_error,
    data_parse_error,
    NULL, /* getParameterEntity */
    NULL, /* cdataBlock; */
    NULL, /* externalSubset; */
    XML_SAX2_MAGIC,
    NULL,
    NULL, /* startElementNsDebug */
    NULL, /* endElementNsDebug */
    NULL
};

// recursively convert records to XML structure
void record_to_xml (abf_flat_t* record, xmlNodePtr parent)
{
    char *name;
    xmlNodePtr node;
    abf_data_type type;
    abf_flat_data_t* current;
    abf_flat_t* list;
    const char *str_type;

    // reset record iterator
    abf_flat_seek(record, 0);

    // add record data
    for(; (current = abf_flat_current(record)); abf_flat_advance(record))
    {
        type = abf_flat_gettype(current);
        str_type = abf_flat_gettypename(type);
        name = abf_flat_getname(current);

        // primitive type?
        if (type != ABF_T_FLAT)
        {
          // convert primitive value to xml char string
          const xmlChar* str_val = (const xmlChar*) abf_flat_tostring(current);
          node = xmlNewTextChild (parent, NULL, (const xmlChar *) str_type, str_val);
        }
        else
        {
            // we've got a list --> recurse
            list = abf_flat_toflat(current);
            node = xmlNewTextChild (parent, NULL, (const xmlChar *) str_type, NULL);
            record_to_xml (list, node);
            abf_destroy (list);
        }

        // add name attribute to node
        if (name != NULL && strlen (name) != 0)
        {
            xmlSetProp (node, (const xmlChar *) "id", (const xmlChar *) name);
        }
    }
}


/* cleanup the xml parser at exit to avoid valgrind reports */
void init_xml()
{
    static int xml_was_init = 0;
    if (0==xml_was_init)
        atexit(xmlCleanupParser);
    xml_was_init = 1;
}

// save record to XML file
abf_status abf_write_xml (abf_flat_t* flat, const char* name)
{
    int retval = -1;
    init_xml();
    // create empty xml document
    xmlDocPtr document = xmlNewDoc ((const xmlChar *) "1.0");

    // create root node
    document->children = xmlNewDocNode (document, NULL, (const xmlChar *) XML_ROOT_NODE, NULL);

  // recursively write content
    record_to_xml (flat, document->children);

    // add checksum as attribute of root node
//     std::ostringstream checksum;
//     checksum << (std::hex) << data.checksum ();
//     xmlSetProp (document->children, (const xmlChar *) "cs", (xmlChar*) checksum.str().c_str());

    // write document to file
    retval = xmlSaveFormatFile (name, document, 1);

    // cleanup
    xmlFreeDoc (document);

    // whether saving succeeded or not
    if (retval!=-1)
        return ABF_OK;
    return ABF_E_PARSER_WRITE_ERROR;
}

abf_status abf_read_xml (abf_flat_t* flat, const char* name)
{
    sax_context_t context;
    abf_flat_t* _flat = abf_create ();
    sax_context_init (&context);
    sax_context_push (&context, _flat);

    init_xml();

    if (xmlSAXUserParseFile (&data_sax_handler, &context, name) != 0)
    {
        fprintf (stderr, "*** disk_writer_xml::get_state: errors while parsing '%s'!\n", name);
        abf_destroy (_flat);
        sax_context_deinit (&context);
        return ABF_E_PARSER_READ_ERROR;
    }

    if(context.error!=ABF_OK)
    {
        abf_destroy (_flat);
        sax_context_deinit (&context);
        return ABF_E_PARSER_READ_ERROR;
    }
    abf_append (flat, _flat);
    abf_destroy (_flat);
    sax_context_deinit (&context);
    return ABF_OK;
}
